#define _GNU_SOURCE
#define CALLSTACK_MAXLEN 64
#include <stdio.h>
#include <dlfcn.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <cxxabi.h>

typedef struct cl // структура, возвращаемая для каждой функции в стеке
{
	const void *address; // указатель рамки const char *function; // имя функции
    __const char *function;
    __const char *object;
}call;


typedef struct mm // Структура, представляющая метаинформацию для некоторой функции.
{
	int len; // количество указателей
	long *offsets; 	// смещение указателей в записи активации относительно указателя на запись активации в стеке
} meta_table;

meta_table *f_meta_glob, *f2_meta_glob, *collect_meta_glob; // метаинформация для каждой из функций

register void *rbp_reg asm ("rbp"); // регистр, в котором хранится текущий указатель рамки может меняться в зависимости от платформы

#define OFFS(x) (long)(reinterpret_cast<long>(rbp_reg) - reinterpret_cast<long>((void*)&(x))) // функция вычисления смещения переменной x относительно указателя на текущую рамку

typedef struct tag_list List;    // структура, реализующая список; используется для хранения метаинформации функций
struct tag_list
{
	meta_table meta; // метаинформация
	char *function_name; // имя соответствующей функции
	List *next; // указатель на следующий элемент списка
	List *prev; // указатель на предыдущий элемент списка
};




List *list_begin, *list_end; // указатель на начало списка и конец списка
int list_size; // размер списка

void add_element_to_the_end (meta_table mt, char *func_name) // функция добавления элемента в список метаинформации
{
	List *new_element = (List*)malloc(sizeof(List));
	new_element->meta = mt;
	new_element->function_name = func_name;
	new_element->next = NULL;
	new_element->prev = list_end;
	if (!list_size)
	{
		list_begin = new_element;
	}
	else
	{
		new_element->prev->next = new_element;
	}
	list_size++;
	list_end = new_element;
}

int backtrace(call *trace, int maxlen) // функция трассировки
{
	Dl_info dlinfo;
	int i;
	for (i = 0; i < maxlen; ++i)
	{
		switch (i)
		{
			case 0:
				if(!__builtin_frame_address(0)) return i; trace[i].address = __builtin_return_address(0); break;
			case 1:
				if(!__builtin_frame_address(1)) return i; trace[i].address = __builtin_return_address(1); break;
			case 2:
				if(!__builtin_frame_address(2)) return i; trace[i].address = __builtin_return_address(2); break;
			case 3:
				if(!__builtin_frame_address(3)) return i; trace[i].address = __builtin_return_address(3); break;
			case 4:
				if(!__builtin_frame_address(4)) return i; trace[i].address = __builtin_return_address(4); break;
			case 5:
				if(!__builtin_frame_address(5)) return i; trace[i].address = __builtin_return_address(5); break;
			case 6:
				if(!__builtin_frame_address(6)) return i; trace[i].address = __builtin_return_address(6); break;
			case 7:
				if(!__builtin_frame_address(7)) return i; trace[i].address = __builtin_return_address(7); break;
			case 8:
				if(!__builtin_frame_address(8)) return i; trace[i].address = __builtin_return_address(8); break; /* etc */
			default:
				return i; break;
		}
		if (dladdr(trace[i].address, &dlinfo) != 0)
		{
			//printf ("All right! %d\n", i);
			//printf ("%p\n", dlinfo.dli_sname);
			const char * dfname;
			dfname=dlinfo.dli_sname;
			int status;
			char * truename;

			truename=__cxxabiv1::__cxa_demangle(dfname,NULL,0,&status);
			
			if (status==0 && truename){
				trace[i].function=truename;  //most part of functions tend to be demangled here
			} else
				trace[i].function = dlinfo.dli_sname; //'main' already has the correct name
			
			trace[i].object = dlinfo.dli_fname;  
		}
	}
	return i;
}

void* built_frame_address(unsigned int X)
{
	switch (X)
	{
		case 0:
			return __builtin_frame_address(0); break;
		case 1:
			return __builtin_frame_address(1); break;
		case 2:
			return __builtin_frame_address(2); break;
		case 3:
			return __builtin_frame_address(3); break;
		case 4:
			return __builtin_frame_address(4); break;
		case 5:
			return __builtin_frame_address(5); break; /* etc */
	}
	return NULL;
}

/* функция, возвращающая метаинформацию по имени функции; **
** в случае, если функции с таким именем нет в списке,    **
** возвращается специальный объект meta table,            **
** у которого поле len равно -1                           */
meta_table get_meta (const char *func_name)
{
	List *list_iterator = list_begin;
	meta_table mt;
	mt.len = -1;

	while (list_iterator != NULL) 	// обход списка метаинформаций
	{
		if (strcmp(list_iterator->function_name, func_name) == 0)
		{
			return list_iterator->meta;
		}
		list_iterator = list_iterator->next;
	}
	return mt;
}

void print_meta(const char *name) // печать метаинформации по имени функции
{
	int i = 0;
	meta_table meta = get_meta(name);
	if (meta.len != -1)
	{
	 	for (i = 0; i < meta.len; i++)
	 	{
	 		printf("offset[%d]=%li ", i, meta.offsets[i]);
	 	}
 	}
 	else
 	{
 		printf("No meta data defined for this function");
 	}
}

// функция построения корневого множества
void collect()
{
	printf("\n collect starts\n");
	//meta_table** meta = (meta_table**)alloca(sizeof(meta_table*));
	//*meta = &collect_meta_glob;
	// выполняем трассировку
	call *trace;
	trace = new call[CALLSTACK_MAXLEN];
	int i = 0;
	int depth = backtrace(trace, CALLSTACK_MAXLEN);
	printf("functions in stack:\n");
	for (i = 0; i < depth; ++i)  // выводим информацию, полученную в результате трассировки
	{
		printf("%s: \n", trace[i].function);
		printf ("Im here! %d\n", i);
		printf(" fp =%p, ", built_frame_address(i + 1));
		print_meta(trace[i].function);
		printf("\n");
	}
	printf("\n collect end\n\n");
}

// функция вычисления метаинформации для функции f;
// последний параметр функции - вычисляемые метаданные
int f_meta_calc (int *a, int b, int *c, meta_table* meta)
{
	// инициализация всех переменных, которые заводятся в функции f;
	// порядок следования аргументов и переменных у функции f и f meta calc
	// должен быть одинаковый
	int z = 0;
	int z1 = 0;
	int z2 = 0;
	int *p;
	int *q;
	// ручное заполнение метаинформации
	meta->len = 4;
	meta->offsets = (long*)malloc(sizeof(long) * meta->len);
	meta->offsets[0] = OFFS(a);
	meta->offsets[1] = OFFS(c);
	meta->offsets[2] = OFFS(p);
	meta->offsets[3] = OFFS(q);
	// печать построенной метаинформации
	printf("\nf_meta_calc starts:\n");
	printf (" a %li \n", OFFS(a));
	printf (" c %li \n", OFFS(c));
	printf (" p %li \n", OFFS(p));
	printf (" q %li \n\n", OFFS(q));
	printf("\nf_meta_calc end\n");
	return 0;
}

// вычисление метаинформации для функции f2;
// аналогична предыдущей функции
int f2_meta_calc (int *a, meta_table *meta)
{
	int y = 0;
	int w = 0;
	int e = 2;
	meta->len = 1;
	meta->offsets = (long*)malloc(sizeof(long) * meta->len);
	meta->offsets[0] = OFFS(a);
	//meta2 = (long)meta;
	printf ("\nf2 meta calc starts: \n");
	printf (" a %li\n", OFFS(a));
	printf ("\nf2 meta calc end. \n");
	return 0;
}

// функция инициализации;
// вычисляет метаинформацию для всех функций
void init ()
{
	list_begin = list_end = NULL;
	list_size = 0;
	meta_table f_meta, f2_meta;
	// вычисление метаинформации функций f и f2
	f_meta_calc(0, 0, 0, &f_meta);
	f2_meta_calc (0, &f2_meta);
	// добавление метаинформации в список
	add_element_to_the_end(f_meta, "f");
	add_element_to_the_end(f2_meta, "f2");
}

// пользовательская функция
int f2 (int*q);
int f (int *a, int b, int *c)
{
	printf("\nf starts:\n");
	int z = 0;
	int z1 = 0;
	int z2 = 0;
	int *p;
	int *q;
	// некоторый код
	// вывод смещений указателей относительно указателя рамки;
	printf("rbp %p %p\n", rbp_reg, __builtin_frame_address(0));
	printf ("a %li \n", OFFS(a));
	printf ("c %li \n", OFFS(c));
	printf ("p %li \n", OFFS(p));
	printf ("q %li \n", OFFS(q));
	printf("\n f end \n");
	f2(a);
	return 0;
}
// пользовательская функция
int f2 (int*q)
{
	int y = 0;
	int w = q[1];
	int e = 2;
	printf("\nf2 starts\n");
	printf("rbp %p %p\n", rbp_reg, __builtin_frame_address(0));
	printf ("q %li \n", OFFS(q));
	printf("\nf2 finish\n");
	collect();
	return 0;
}

int main (int argc, char **argv)
{
	printf("main rbp %p\n", rbp_reg);
	int n;
	int *mas = (int*)malloc(sizeof(int) * 4);
	init ();
	f(mas, 0, mas); //changed
	return 0;
}